﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

using Axiom;
using Axiom.Core;
using Axiom.Graphics;
using Axiom.Configuration;
using Axiom.Math;
using Axiom.Overlays;
using Axiom.Input;
using Axiom.Animating;
using VisualDM.Model;

using IDE;
using Eco.ObjectRepresentation;

namespace Visualizator
{

    public class Line3d : SimpleRenderable
    {
        // constants for buffer source bindings
        const int POSITION = 0;
        const int COLOR = 1;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="startPoint">Point where the line will start.</param>
        /// <param name="direction">The direction the vector is heading in.</param>
        /// <param name="length">The length (magnitude) of the line vector.</param>
        /// <param name="color">The color which this line should be.</param>
        public Line3d(Vector3 startPoint, Vector3 direction, float length, ColorEx color)
        {
            // normalize the direction vector to ensure all elements fall in [0,1] range.
            direction.Normalize();

            // calculate the actual endpoint
            Vector3 endPoint = startPoint + (direction * length);

            vertexData = new VertexData();
            vertexData.vertexCount = 2;
            vertexData.vertexStart = 0;

            VertexDeclaration decl = vertexData.vertexDeclaration;
            VertexBufferBinding binding = vertexData.vertexBufferBinding;

            // add a position and color element to the declaration
            decl.AddElement(POSITION, 0, VertexElementType.Float3, VertexElementSemantic.Position);
            decl.AddElement(COLOR, 0, VertexElementType.Color, VertexElementSemantic.Diffuse);

            // create a vertex buffer for the position
            HardwareVertexBuffer buffer =
                HardwareBufferManager.Instance.CreateVertexBuffer(
                decl.GetVertexSize(POSITION),
                vertexData.vertexCount,
                BufferUsage.StaticWriteOnly);

            Vector3[] pos = new Vector3[] { startPoint, endPoint };

            // write the data to the position buffer
            buffer.WriteData(0, buffer.Size, pos, true);

            // bind the position buffer
            binding.SetBinding(POSITION, buffer);

            // create a color buffer
            buffer = HardwareBufferManager.Instance.CreateVertexBuffer(
                decl.GetVertexSize(COLOR),
                vertexData.vertexCount,
                BufferUsage.StaticWriteOnly);

            int colorValue = Root.Instance.RenderSystem.ConvertColor(color);

            int[] colors = new int[] { colorValue, colorValue };

            // write the data to the position buffer
            buffer.WriteData(0, buffer.Size, colors, true);

            // bind the color buffer
            binding.SetBinding(COLOR, buffer);

            // MATERIAL
            // grab a copy of the BaseWhite material for our use
            Material material;
            if (MaterialManager.Instance.GetByName("LineMat") == null)
            {
                material = (Material)MaterialManager.Instance.GetByName("BaseWhite");
                material = material.Clone("LineMat");
                material.Lighting = false;
                // set culling to none so the triangle is drawn 2 sided
                material.CullingMode = CullingMode.None;
            }
            else material = (Material)MaterialManager.Instance.GetByName("LineMat");
            // disable lighting to vertex colors are used

            this.Material = material;

            // set the bounding box of the line
            this.box = new AxisAlignedBox(startPoint, endPoint);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="camera"></param>
        /// <returns></returns>
        public override float GetSquaredViewDepth(Camera camera)
        {
            Vector3 min, max, mid, dist;
            min = box.Minimum;
            max = box.Maximum;
            mid = ((min - max) * 0.5f) + min;
            dist = camera.DerivedPosition - mid;

            return dist.LengthSquared;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="op"></param>
        public override void GetRenderOperation(RenderOperation op)
        {
            op.vertexData = vertexData;
            op.indexData = null;
            op.operationType = OperationType.LineList;
            op.useIndices = false;
        }

        public override float BoundingRadius
        {
            get
            {
                return 0;
            }
        }

    }

    public static class VisualizationService
    {
        public static bool AABBIntersectAABB(AxisAlignedBox a, AxisAlignedBox b)
        {
            return Axiom.Math.Utility.Intersects(a,b)!=Intersection.None;
        }
    }

    public interface IVisualizationService
    {
        void LoadFirst();
        void Reload();
        void Stop();
    }

    public class Visualizator:IVisualizationService
    {
        private IEcoServiceProvider serviceProvider;

        public VisualDM.Model.VisualDomainModel VDM;
        Control application;

        private string sceneConfigFile = "EngineConfig.xml";
        private string sceneLogFile = "AxiomEngine.log";
        public Root sceneRoot;
        public SceneManager sceneManager;
        private List<RenderWindow> renderWindows = new List<RenderWindow>();
        private List<Viewport> sceneViewports = new List<Viewport>();
        private InputReader inputReader;
        Vector3 camAccel = Vector3.Zero;
        float cameraScale;
        Vector3 camVelocity = Vector3.Zero;
        CameraSceneObject sceneCamera;
        private bool IsRunning = false;

        public void SetAmbient(ColorEx c)
        {
            sceneManager.AmbientLight = c;

        }

        public Visualizator(IEcoServiceProvider serviceProvider, Control app, VisualDM.Model.VisualDomainModel vdm)
        {
            VDM = vdm;
            serviceProvider = serviceProvider;
            application = app;
            VDM = vdm;
            Setup();
        }

        public void AddSceneObject(SceneObject obj)
        {
            VDM.Scene.AddChildSceneObject(obj);
        }

        #region Check Points
        public void SetCheckPoint()
        {
            VDM.Scene.SetCheckPoint();
        }

        public void RollBack()
        {
            VDM.Scene.RollBack();
        }
        #endregion

        #region Loading

        protected void Setup()
        {
            if (sceneRoot == null)
            {
                sceneRoot = new Root(sceneLogFile);
                SetupResources();
                sceneRoot.RenderSystem = sceneRoot.RenderSystems["Direct3D9"];
                sceneRoot.Initialize(false);
                Axiom.Core.ResourceGroupManager.Instance.InitializeAllResourceGroups();
                sceneManager = sceneRoot.SceneManagers.GetSceneManager(SceneType.Generic);

                inputReader = PlatformManager.Instance.CreateInputReader();
            }
        }

        protected void SetupResources()
        {
            if (File.Exists(Path.GetFullPath(sceneConfigFile)))
            {
                EngineConfig config = new EngineConfig();
                config.ReadXml(sceneConfigFile);
                foreach (EngineConfig.FilePathRow row in config.FilePath)
                {
                    ResourceGroupManager.Instance.AddResourceLocation(row.src, row.type);
                }
            }
        }

        public void LoadFirst()
        {
        }

        public void Reload()
        {
        } 

        #endregion

        #region Rendering
        
        public void StartRender()
        {
/*            MaterialManager.Instance.SetDefaultTextureFiltering(FilterOptions.Anisotropic, FilterOptions.Anisotropic, FilterOptions.Anisotropic);
            sceneManager.AmbientLight = new Axiom.Core.ColorEx(1,0.5f,0.5f,0.5f);
            sceneManager.ShadowColor = new Axiom.Core.ColorEx(1,1,1,1);
            sceneManager.ShadowFarDistance = 10000;*/

            SceneNode place = sceneManager.RootSceneNode.CreateChildSceneNode("Place");
            Plane plane = new Plane(Vector3.UnitY, 0.0f);
            MeshManager.Instance.CreatePlane("ground", "GroundGroup", plane, 300.0f, 300.0f, 20, 20, true, 1, 5.0f, 5.0f, Vector3.UnitZ);
            Entity entity = sceneManager.CreateEntity("GroundEntity", "ground");
            place.AttachObject(entity);
            entity.MaterialName = "Place";
//            entity.CastShadows = true;
            place.Translate(new Vector3(0, -20.5f, 50));

            SceneNode Grid = sceneManager.RootSceneNode.CreateChildSceneNode("Grid");
            //columns

//            sceneManager.

            for (int i = -150; i <= 150; i = i + 15)
                Grid.AttachObject(new Line3d(new Vector3(-150, 0.0f, i), Vector3.UnitX, 300, ColorEx.Black));
            //rows
            for (int i = -150; i <= 150; i = i + 15)
                Grid.AttachObject(new Line3d(new Vector3(i, 0.00f, -150), Vector3.UnitZ, 300, ColorEx.Black));

            Grid.Translate(new Vector3(0,-20.0f,50));

/*            sceneManager.SetShadowTextureSettings(512, 2);
            sceneManager.ShadowColor = new ColorEx(0.5f, 0.5f, 0.5f);
            sceneManager.ShadowFarDistance = 100000;*/
//            sceneManager.AmbientLight = new ColorEx(1.0f, 0.5f, 0.5f, 0.5f);
//            sceneManager.AmbientLight = ColorEx.Gray;
            IsRunning = true;

/*            //            VDM.Scene.ResetToInitPositions();
            tr
            {
                sceneRoot.StartRendering();
            }
            catch (System.Reflection.ReflectionTypeLoadException ex)
            {
                for (int i = 0; i < ex.LoaderExceptions.Length; i++)
                    if (LogManager.Instance != null)
                        LogManager.Instance.Write(ex.LoaderExceptions[i].Message);
            }
            catch (Exception ex)
            {
                if (LogManager.Instance != null)
                    LogManager.Instance.Write(ex.ToString());
            }*/
        }

        public void Render(object source, FrameEventArgs e)
        {
            VDM.Scene.Idle(e);
        }

        public void RenderFrame()
        {
            VDM.Scene.Idle(null);
            sceneRoot.RenderOneFrame();
        }

        public void StopRender()
        {
            IsRunning = false;
            sceneRoot.QueueEndRendering();
        }

        public void FreeRenderContext()
        {
            sceneRoot.QueueEndRendering();
            sceneRoot.FrameStarted -= UpdateInput;
            sceneRoot.FrameStarted -= Render;
            //TODO: Target.Resize -= visualizationTarget_Resize;

            sceneManager.DestroyAllAnimations();
            sceneManager.DestroyAllAnimationStates();
            sceneManager.DestroyAllOverlays();
            sceneManager.DestroyAllStaticGeometry();

            sceneManager.RemoveAllBillboardSets();
            sceneManager.RemoveAllCameras();

            sceneManager.RemoveAllEntities();
            sceneManager.RemoveAllLights();

            inputReader.Dispose();
            inputReader = null;

            for (int i = 0; i < renderWindows.Count; i++)
            {
                RenderWindow w;
                w = renderWindows[i];
                sceneRoot.DetachRenderTarget(w);
                renderWindows.RemoveAt(i);
                w.Dispose();
                w = null;
            }
            sceneManager = null;
            sceneRoot.Dispose();
            sceneRoot = null;
        }

        public void Stop()
        {
        } 

        #endregion

        public void ResetToInitPositions()
        {
            VDM.Scene.ResetToInitPositions();
        }

        #region Targets & ViewPorts

        private void visualizationTarget_Resize(object sender, EventArgs e)
        {
            renderWindows[0].WindowMovedOrResized();
        }
        
        public void AddRenderWindow(Control Target, CameraSceneObject cam/*не должна была раньше вызываться ф-я Initialize()*/)
        {
            Axiom.Collections.NamedParameterList paramList = new Axiom.Collections.NamedParameterList();

            paramList["externalWindowHandle"] = Target.Handle;
            paramList["depthBuffer"] = true;
            paramList["colorDepth"] = 32;
            paramList["FSAA"] = 0;
            paramList["FSAAQuality"] = 0;

            renderWindows.Add(sceneRoot.CreateRenderWindow("RenderWindow" + renderWindows.Count.ToString(), Target.Width, Target.Height, false, paramList));

            // camera
            cam.Initialize(sceneManager.RootSceneNode, sceneManager,false);
            cam.SetFOV(45/*Axiom.Math.Utility.PI*/);
            if (VDM.FindSceneObject(cam.Name, cam.ID) == null)
                AddSceneObject(cam);

            if (renderWindows.Count == 1)
                sceneCamera = cam;

            sceneManager.ShadowTechnique = ShadowTechnique.StencilAdditive;

            if (renderWindows.Count == 1)
                inputReader.Initialize(renderWindows[0], true, false, false, true);

            sceneViewports.Add(renderWindows[renderWindows.Count - 1].AddViewport(cam.camera));
            sceneViewports[sceneViewports.Count - 1].BackgroundColor = ColorEx.Silver;//ColorEx.Black;

            cam.SetAspectRatio(sceneViewports[sceneViewports.Count - 1].ActualWidth / sceneViewports[sceneViewports.Count - 1].ActualHeight);
            Target.Resize += new EventHandler( visualizationTarget_Resize);

            if (renderWindows.Count == 1)
                TextureManager.Instance.DefaultMipmapCount = 5;

            Target.Tag = cam;

            sceneRoot.MaxFramesPerSecond = 60;
        }

        public void SetActiveViewPort(CameraSceneObject cam)
        {
            sceneCamera = (VDM.FindSceneObject(cam.Name, cam.ID) != null) ? cam : sceneCamera;
        } 

        #endregion

        #region Event handlers
        
        private void UpdateInput(object source, FrameEventArgs e)
        {
            if (application.ContainsFocus)
            {
                camAccel = Vector3.Zero;
                cameraScale = 100 * e.TimeSinceLastFrame;
                float scaleMove = 200 * e.TimeSinceLastFrame;
                /*******************************************/

                #region Camera movement
                // Reset vectors
                inputReader.Capture();

                if (inputReader.IsKeyPressed(KeyCodes.NumPad4/*.A*/))
                {
                    camAccel.x = -2.0f;
                }
                if (inputReader.IsKeyPressed(KeyCodes.NumPad6/*.D*/))
                {
                    camAccel.x = 2.0f;
                }
                if (inputReader.IsKeyPressed(KeyCodes.NumPad8/*.W*/))
                {
                    camAccel.z = -2.5f;
                }
                if (inputReader.IsKeyPressed(KeyCodes.NumPad2/*.S*/))
                {
                    camAccel.z = 2.5f;
                }

                if (inputReader.IsKeyPressed(KeyCodes.NumPad7/*.Left*/))
                {
                    sceneCamera.Yaw(cameraScale);
                }

                if (inputReader.IsKeyPressed(KeyCodes.NumPad9/*.Right*/))
                {
                    sceneCamera.Yaw(-cameraScale);
                }

                if (inputReader.IsKeyPressed(KeyCodes.NumPad3/*.Up*/))
                {
                    sceneCamera.Pitch(cameraScale);
                }

                if (inputReader.IsKeyPressed(KeyCodes.NumPad1/*.Down*/))
                {
                    sceneCamera.Pitch(-cameraScale);
                }

                if (!inputReader.IsMousePressed(Axiom.Input.MouseButtons.Left))
                {
                    float cameraYaw = -inputReader.RelativeMouseX * .13f;
                    float cameraPitch = -inputReader.RelativeMouseY * .13f;

                    sceneCamera.Yaw(cameraYaw);
                    sceneCamera.Pitch(cameraPitch);
                }

                if (inputReader.IsKeyPressed(KeyCodes.B))
                {
                    sceneManager.ShowBoundingBoxes = !sceneManager.ShowBoundingBoxes;
                }

/*                if (inputReader.IsKeyPressed(KeyCodes.D1))
                {

                    Vector3 v1 = sceneManager.RootSceneNode.WorldAABB.Corners[5] - sceneManager.RootSceneNode.WorldAABB.Corners[4];
                    Vector3 v2;
                    v2.x = v1.x * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                    v2.y = v1.y * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                    v2.z = v1.z * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                    float len = v2.Length * 2;
                    Vector3 v3;
                    v3.x = sceneCamera.Direction.x * len;
                    v3.y = sceneCamera.Direction.y * len;
                    v3.z = sceneCamera.Direction.z * len;
                    sceneCamera.Position = sceneManager.RootSceneNode.WorldAABB.Center - v3;
                    sceneCamera.LookAt(sceneManager.RootSceneNode.WorldAABB.Center);
                }*/



                camVelocity = (camAccel * scaleMove /* camSpeed*/);

                sceneCamera.MoveRelative(camVelocity/* * e.TimeSinceLastFrame */);

                if (camAccel == Vector3.Zero)
                {
                    camVelocity *= (1 - (6 * e.TimeSinceLastFrame));
                }
                #endregion Camera movement
            }
        }

        public void HandleKeyPress(object sender, KeyPressEventArgs e)
        {
            var args = new PreviewKeyDownEventArgs((Keys)e.KeyChar);
            HandleKeyDown(sender, args);
            e.Handled = args.IsInputKey;
        }

        public void HandleKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            camAccel = Vector3.Zero;
            cameraScale = 1;
            float scaleMove = 2;

            if (e.KeyData == Keys.NumPad4/*.A*/)
            {
                camAccel.x = -2.0f;
            }
            if (e.KeyData == Keys.NumPad6/*.D*/)
            {
                camAccel.x = 2.0f;
            }
            if (e.KeyData == Keys.NumPad8/*.W*/)
            {
                camAccel.z = -2.5f;
            }
            if (e.KeyData == Keys.NumPad2/*.S*/)
            {
                camAccel.z = 2.5f;
            }

            if (e.KeyData == Keys.NumPad7/*.Left*/)
            {
                sceneCamera.Yaw(cameraScale);
            }

            if (e.KeyData == Keys.NumPad9/*.Right*/)
            {
                sceneCamera.Yaw(-cameraScale);
            }

            if (e.KeyData == Keys.NumPad3/*.Up*/)
            {
                sceneCamera.Pitch(cameraScale);
            }

            if (e.KeyData == Keys.NumPad1/*.Down*/)
            {
                sceneCamera.Pitch(-cameraScale);
            }

            //if (!inputReader.IsMousePressed(Axiom.Input.MouseButtons.Left))
            //{
            //    float cameraYaw = -inputReader.RelativeMouseX * .13f;
            //    float cameraPitch = -inputReader.RelativeMouseY * .13f;

            //    sceneCamera.Yaw(cameraYaw);
            //    sceneCamera.Pitch(cameraPitch);
            //}

            if (e.KeyData == Keys.B)
            {
                sceneManager.ShowBoundingBoxes = !sceneManager.ShowBoundingBoxes;
            }

/*            if (e.KeyData == Keys.D1)
            {

                Vector3 v1 = sceneManager.RootSceneNode.WorldAABB.Corners[5] - sceneManager.RootSceneNode.WorldAABB.Corners[4];
                Vector3 v2;
                v2.x = v1.x * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                v2.y = v1.y * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                v2.z = v1.z * (float)(1 / Math.Tan(sceneCamera.GetFOV() / 2));
                float len = v2.Length * 2;
                Vector3 v3;
                v3.x = sceneCamera.Direction.x * len;
                v3.y = sceneCamera.Direction.y * len;
                v3.z = sceneCamera.Direction.z * len;
                sceneCamera.Position = sceneManager.RootSceneNode.WorldAABB.Center - v3;
                sceneCamera.LookAt(sceneManager.RootSceneNode.WorldAABB.Center);
            }*/



            camVelocity = (camAccel * scaleMove /* camSpeed*/);

            sceneCamera.MoveRelative(camVelocity/* * e.TimeSinceLastFrame */);

            if (camAccel == Vector3.Zero)
            {
                camVelocity *= 1;
            }
        }

        #endregion
    }
}
